/*
 * dbgmsg.c
 *
 *  Created on: May 16, 2013
 *      Author: Lucifer
 */
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#ifdef	_WIN32
#include <windows.h>
#define	snprintf	_snprintf
#pragma warning(disable:4996)
#else
#include <pthread.h>
#include <stdarg.h>
#endif

#define	UNUSED(v)	(v);

#include "dbgmsg.h"

/* size of output buffer (arbitrary) */
#define	DBG_BUFFER_SIZE	20000

/* number of ENTRY nesting levels to indicate with a '.' */
#define	MAX_NESTING	50

/* output level names declaration */
static const char *dbg_level_names[] =
{ "INFO", "TRACE", "WARN", "ERROR", "ASSERT" };

static long		dbg_initialized = 0;
static FILE*	dbg_out_file = NULL;

#ifdef	_WIN32
static CRITICAL_SECTION	dbg_crit_section;
#else
static pthread_mutex_t	dbg_mutex;
#endif

/**
 * The global debug trace level.
 */
unsigned int dbg_level	= 0xFFFF;

/**
 * Initializes the debug trace module.
 * @note Actually, We don't need to call this function manually.
 */
void dbg_printf_initialize()
{
	int retval;

	if (dbg_initialized)
		return;

#ifdef	_WIN32
	UNUSED(retval);
	InitializeCriticalSection(&dbg_crit_section);
#else
	retval = pthread_mutex_init(&dbg_mutex, NULL);
	assert(retval == 0);
#endif

	/* TODO: we should load configuration settings */
	dbg_out_file = stdout;
	dbg_level = 0xFFFF;

	dbg_initialized = 1;
}

/**
 * Finalizes the debug trace module.
 * @note Actually, We don't need to call this function manually.
 */
void dbg_printf_cleanup()
{
	int retval;

	if (!dbg_initialized)
		return;
#ifdef	_WIN32
	UNUSED(retval);
	DeleteCriticalSection(&dbg_crit_section);
#else
	retval = pthread_mutex_destroy(&dbg_mutex);
	assert(retval == 0);
#endif

	dbg_out_file = NULL;
	dbg_level = 0;

	dbg_initialized = 0;
}

/**
 * Print a debug trace message.
 * @param[in] level
 * @param[in] channel
 * @param[in] function
 * @param[in] file
 * @param[in] line
 * @param[in] format
 */
void dbg_printf(DBG_LEVEL_ID level, const char* channel, const char* function, const char* file, int line,
		const char* format, ...)
{
	char buffer[DBG_BUFFER_SIZE];
	char indent[MAX_NESTING];
	char* buffer_ptr;
	int output_size;
	va_list args;
	long thread_id;
	int old_errno;

	dbg_printf_initialize();

	indent[0] = 0;
	old_errno = errno;
#ifdef	_WIN32
	thread_id = GetCurrentThreadId();
#else
	thread_id = (long) pthread_self();
#endif

	output_size = snprintf(buffer, sizeof(buffer), "{%08lX} %-5s [%-7s] at %s.%d: ", thread_id, dbg_level_names[level],
			channel, function, line);

	if (output_size + 1 > sizeof(buffer))
	{
		fprintf(stderr, "ERROR : buffer overflow in dbg_printf_gcc");
		return;
	}

	buffer_ptr = buffer + output_size;

	va_start(args, format);
	output_size += vsnprintf(buffer_ptr, DBG_BUFFER_SIZE - output_size, format, args);
	va_end(args);

	if (output_size > DBG_BUFFER_SIZE)
	{
		fprintf(stderr, "ERROR : buffer overflow in dbg_printf_gcc");
	}

#ifdef	_WIN32
	EnterCriticalSection(&dbg_crit_section);
#else
	pthread_mutex_lock(&dbg_mutex);
#endif
	fprintf(dbg_out_file, "%s%s\n", indent, buffer);
#ifdef	_WIN32
	LeaveCriticalSection(&dbg_crit_section);
#else
	pthread_mutex_unlock(&dbg_mutex);
#endif

	if (fflush(dbg_out_file) != 0)
	{
		fprintf(stderr, "ERROR : fflush() failed errno:%d (%s)\n", errno, strerror(errno));
	}

	if (old_errno != errno)
	{
		fprintf(stderr, "ERROR: errno changed by DBG_printf_gcc\n");
		errno = old_errno;
	}

	return;
}
